/*
 *  ifxconversion.cpp - conversions utilities implementation
 *
 *    Copyright (c) 2001-2006, Luxena Software Company. All rights reserved.
 *
 *  Purpose:
 *
 */

#include <memory.h>
#include <math.h>
#include <ifxtypes.h>
#include <sqlhdr.h>
#include "dbxpress.hpp"
#include "ifxdefs.hpp"


/////////////////////////////////////////////////////////////////////////////
// BCD convertion routines

void bcd_to_decimal(FMTBcd *pbcd, decimal *pdec)
{
	int iPrecision;

	// if precision is 0 then value is 0
	//
	if ((iPrecision = pbcd->iPrecision) == 0)
	{
		pdec->dec_ndgts = 0;
		return;
	}

	// analyze is bcd precision odd number
	//
	bool bPrecisionIsOdd = pbcd->iPrecision & 0x01;

	int iSignSpecialPlaces;

	// set decimal sign
	//
	if ((iSignSpecialPlaces = pbcd->iSignSpecialPlaces) & 0x40)
		pdec->dec_pos = -1;
	else
		pdec->dec_pos = (iSignSpecialPlaces ^ 0x80) >> 7;

	// find first non zero byte
	//
	int iNonZero = 0;
	pBYTE pNibble = pbcd->iFraction;
	for (; *pNibble == 0; pNibble++, iNonZero++);

	// evaluate 10 based exponent of decimal
	//
	int dec_exp = iPrecision - (iSignSpecialPlaces & 0x3f);

	// convert bcd nibbles into decimal digits, count number of significant
	// digits and evaluate 100 based decimal exponent
	//
	int dec_ndgts;
	char *pDigit = pdec->dec_dgts;
	if (dec_exp & 0x01)
	{
		// in this case it is needed to shift digits for one position to right

		if (*pNibble & 0xf0)
		{
			*pDigit++ = *pNibble >> 4;
			dec_exp++;
			dec_ndgts = 1;
		}
		else
			dec_ndgts = 0;

		--iPrecision >>= 1;

		for (int i = iNonZero; i < iPrecision; i++)
		{
			BYTE nibble = *pNibble++; 
			*pDigit++ = (nibble & 0x0f)*10 + (*pNibble >> 4);
			dec_ndgts++;
		}

		if (!bPrecisionIsOdd)
		{
			*pDigit = (*pNibble & 0x0f)*10;
			dec_ndgts++;
		}
	}
	else
	{
		iPrecision >>= 1;

		dec_ndgts = 0;
		for (int i = iNonZero; i < iPrecision; i++)
		{
			*pDigit++ = (*pNibble >> 4)*10 + (*pNibble & 0x0f);
			pNibble++;
			dec_ndgts++;
		}

		if (bPrecisionIsOdd)
		{
			*pDigit = (*pNibble >> 4)*10;
			dec_ndgts++;
		}
	}

	// set decimal exponent
	//
	pdec->dec_exp = (dec_exp >> 1) - iNonZero;

	// set decimal significant digits number
	//
	pdec->dec_ndgts = dec_ndgts;
}

void decimal_to_bcd(decimal *pdec, FMTBcd *pbcd, int4 iColPrec, int4 iColScale)
{
	// clear destinition value
	memset(pbcd, 0, sizeof(*pbcd));

	int dec_ndgts, dec_exp;

	// if no signicant digits nothing to convert
	//
	if ((dec_ndgts = pdec->dec_ndgts) == 0)
		return;

	BYTE iPrecision, iFraction;
	BYTE *pNibble = pbcd->iFraction;

	// evaluate bcd precision and scale of decimal
	//
	if ((dec_exp = pdec->dec_exp) < 0)
	{
		int cLeadZeros = -dec_exp;
		iPrecision = static_cast<BYTE>(2*(dec_ndgts + cLeadZeros));
		iFraction = iPrecision;
		pNibble = &pNibble[cLeadZeros];
	}
	else if (dec_exp < dec_ndgts)
	{
		iPrecision = static_cast<BYTE>(2*dec_ndgts);
		iFraction = static_cast<BYTE>(2*(dec_ndgts - dec_exp));
	}
	else
	{
		iPrecision = static_cast<BYTE>(2*dec_exp);
		iFraction = 0;
	}

	// convert decimal digits into bcd nibbles
	//
	char *pDigit = pdec->dec_dgts;
	char chDigit = *pDigit;
	char chDigitHigh = chDigit / 10, chDigitLow;
	if ((chDigitHigh == 0) && (iColPrec - iColScale & 1) && (iFraction < iPrecision))
	{
		for (int i = 1; i < pdec->dec_ndgts; i++, pNibble++)
		{
			*pNibble = (chDigit % 10) << 4;
			*pNibble |= (chDigit = *++pDigit) / 10;
		}
		*pNibble = chDigitLow = (chDigit % 10) << 4;
		iPrecision--;
	}
	else
	{
		chDigitLow = chDigit % 10;
		*pNibble++ = (chDigitHigh << 4) | chDigitLow;
		for (int i = 1; i < pdec->dec_ndgts; i++, pNibble++)
		{
			chDigit = *++pDigit;
			chDigitLow = chDigit % 10;
			*pNibble = ((chDigit / 10) << 4) | chDigitLow;
		}
	}

	if (chDigitLow == 0 && iFraction > 0)
	{
		iPrecision--;
		iFraction--;
	}

	// set precision, sign bit, null bit and fraction count
	//
	pbcd->iPrecision = iPrecision;
	pbcd->iSignSpecialPlaces = 
		(pdec->dec_pos == 0 ? 0x80 : 0x00) |
		(pdec->dec_pos == -1 ? 0x40 : 0x00) |
		(iFraction);
}

DFLOAT decimal_to_double(decimal *pdec)
{
	if (pdec->dec_ndgts == 0)
		return 0;
	double result = 0;
	double base = pow(10.0, (pdec->dec_exp - 1) << 1);
	char *digit = pdec->dec_dgts;
	for (int i = 0; i < pdec->dec_ndgts; i++) {
		result += *digit++ * base;
		base /= 100;
	}
	if (pdec->dec_pos == 0)
		result *= -1;
	return result;
}


/////////////////////////////////////////////////////////////////////////////
// int8 convertion routines

void int8_to_bcd(ifx_int8 *pint8, FMTBcd *pbcd)
{
	BYTE digits[INT8_AS_DECIMAL_PRECISION];
	int precision = 0;
	uint4 low = pint8->data[0];
	uint4 high = pint8->data[1];
	pBYTE digit = &digits[INT8_AS_DECIMAL_PRECISION];
	while (high != 0) {
		uint4 high_remainder = high % 10;
		uint4 full_remainder = high_remainder*6 + low % 10;
		high /= 10;
		(low /= 10) += (high_remainder*429496729) + (full_remainder/10);
		*(--digit) = static_cast<BYTE>(full_remainder % 10);
		precision++;
	}
	while (low != 0) {
		*(--digit) = static_cast<BYTE>(low % 10);
		low /= 10;
		precision++;
	}
	pbcd->iSignSpecialPlaces = pint8->sign < 0 ? 0x80 : 0x00;
	pbcd->iPrecision = precision;
	pBYTE nibble = pbcd->iFraction;
	while (precision-- > 0)
	{
		*nibble = *digit++ << 4;
		if (precision-- == 0)
			break;
		*nibble++ |= *digit++;
	}
}

DFLOAT int8_to_double(ifx_int8 *pint8)
{
	return pint8->sign*(pint8->data[1]*4294967296.0 + pint8->data[0]);
}


/////////////////////////////////////////////////////////////////////////////
// TimeStamp conversion routines

void ctimestamp_to_dtime (pCTIMESTAMP ptstmp, dtime *pdtime)
{
	// set datetime qualifier
	//
	pdtime->dt_qual = TU_CURRQUAL;

	dec_t *pdec = &pdtime->dt_dec;
	// to indicate that we supply all 7 parts of date time (from fraction(1) to year(7))
	pdec->dec_exp = 7;
	pdec->dec_pos = 1;
	pdec->dec_ndgts = 9;
	char *pdgt = pdec->dec_dgts;

	// set date
	//
	int year = ptstmp->year;
	*pdgt++ = year / 100;
	*pdgt++ = year % 100;
	*pdgt++ = static_cast<char>(ptstmp->month);
	*pdgt++ = static_cast<char>(ptstmp->day);

	// set time
	//
	*pdgt++ = static_cast<char>(ptstmp->hour);
	*pdgt++ = static_cast<char>(ptstmp->minute);
	*pdgt++ = static_cast<char>(ptstmp->second);

	// set fraction
	//
	int fraction = ptstmp->fraction;
	*pdgt++ = fraction / 10;
	*pdgt = (fraction % 10)*10;
}

void timestamp_to_dtime (DFLOAT stmp, dtime *pdtime)
{
	long date = static_cast<long>(stmp / DAY_MSECS) - DATE_DELTA;
	long time = static_cast<long>(fmod(stmp, DAY_MSECS));

	CTIMESTAMP tstmp;

	// get year, month, day
	//
	rjulmdy(date, reinterpret_cast<short *>(&tstmp.month));
	tstmp.year = tstmp.hour;

	// get hour, minute, second and fraction
	//
	int i;
	tstmp.fraction = time % 1000;
	tstmp.second = (i = time / 1000) % 60;
	tstmp.minute = (i /= 60) % 60;
	tstmp.hour = i / 60;

	ctimestamp_to_dtime(&tstmp, pdtime);
}

void time_to_dtime (INT32 time, dtime *pdtime)
{
	// set datetime qualifier
	//
	pdtime->dt_qual = TU_ENCODE(11, TU_HOUR, TU_F3);

	// set decimal part
	//
	dec_t *pdec = &pdtime->dt_dec;
	// to indicate that we supply 4 parts of date time (from fraction(1) to hour(4))
	pdec->dec_exp = 4;
	pdec->dec_pos = 1;
	pdec->dec_ndgts = 5;
	char *pdgt = &pdec->dec_dgts[4];
	int i = time % 1000;
	*pdgt-- = (i % 10)*10;
	*pdgt-- = i / 10;
	*pdgt-- = static_cast<char>((i = time / 1000) % 60);
	*pdgt-- = static_cast<char>((i /= 60) % 60);
	*pdgt = static_cast<char>(i / 60);
}

void dtime_to_ctimestamp (dtime *pdtime, pCTIMESTAMP ptstmp)
{
	const CTIMESTAMP tsNullDate = { 1900, 1, 1, 0, 0, 0, 0 };
	memcpy(ptstmp, &tsNullDate, sizeof(CTIMESTAMP));

	decimal *pdec = &pdtime->dt_dec;
	int dec_ndgts;

	// if no signicant digits nothing to convert
	//
	if ((dec_ndgts = pdec->dec_ndgts) == 0) return;

	int dgt_pos = 0;
	char *pdgt = pdec->dec_dgts;

	int tu_pos = TU_FRAC - (pdec->dec_exp << 1);
	if (tu_pos <= 0) (tu_pos >>= 1)++;
	INT16 *pfld = reinterpret_cast<INT16 *>(ptstmp);

// check for exceeding significant digits
//
#define checkDigit if (++dgt_pos >= dec_ndgts) return;

	// set year
	//
	if (tu_pos < TU_MONTH)
	{
		if (tu_pos == TU_YEAR)
		{
			*pfld = *pdgt++*100;
			checkDigit;
			tu_pos++;
			*pfld++ += *pdgt++;
		}
		else
			*pfld++ = *pdgt++;
		checkDigit;
		tu_pos++;
	}
	else
		pfld = reinterpret_cast<INT16 *>(&reinterpret_cast<char *>(pfld)[tu_pos]);

	// set month, day, hour, minute and second
	//
	while (tu_pos < TU_FRAC)
	{
		*pfld++ = *pdgt++;
		checkDigit;
		tu_pos += 2;
	}

	// set fraction
	//
	*(UINT32 *)pfld = *pdgt++ * 10;
	checkDigit;
	*(UINT32 *)pfld += *pdgt / 10;
}

int writeDigitsAsChars (char*& buffer, char*& digit, int& digit_count,
	int& unit_of_digit, int current_time_unit, int next_time_unit, bool& allow_leading_zero)
{
	if (unit_of_digit < next_time_unit && digit_count != 0)
	{
		while (unit_of_digit < next_time_unit && digit_count != 0)
		{
			char high_digit = *digit / 10;
			if (allow_leading_zero || high_digit != 0)
				*buffer++ = high_digit + '0';
			unit_of_digit++;
			if (unit_of_digit < next_time_unit) {
				*buffer++ = *digit++ % 10 + '0';
				unit_of_digit++;
			}
			digit_count--;
		}
		for (int i = unit_of_digit; i < next_time_unit; i++)
			*buffer++ = '0';
	} else
		if (allow_leading_zero) {
			for (int i = current_time_unit; i < next_time_unit; i++)
				*buffer++ = '0';
		} else
			*buffer++ = '0';

	allow_leading_zero = true;

	return next_time_unit;
}

void interval_to_char(intrvl *pintrvl, char *pbuffer)
{
	int2 tu_start = TU_START(pintrvl->in_qual);
	int2 tu_end = TU_END(pintrvl->in_qual);

	int digit_count = pintrvl->in_dec.dec_ndgts;
	char *digit = pintrvl->in_dec.dec_dgts;
	int unit_of_digit = TU_FRAC - (pintrvl->in_dec.dec_exp << 1);
	bool allow_leading_zero = false;

	if (tu_start < TU_DAY)
	{
		if (tu_start == TU_YEAR) {
			tu_start = writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_YEAR, TU_MONTH, allow_leading_zero);
			if (tu_end > TU_YEAR)
				*pbuffer++ = '-';
		}
		if (tu_start == TU_MONTH)
			writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_MONTH, TU_DAY, allow_leading_zero);
	}
	else
	{
		if (tu_start == TU_DAY) {
			tu_start = writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_DAY, TU_HOUR, allow_leading_zero);
			if (tu_end > TU_DAY)
				*pbuffer++ = ' ';
		}
		if (tu_start == TU_HOUR) {
			tu_start = writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_HOUR, TU_MINUTE, allow_leading_zero);
			if (tu_end > TU_HOUR)
				*pbuffer++ = ':';
		}
		if (tu_start == TU_MINUTE) {
			tu_start = writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_MINUTE, TU_SECOND, allow_leading_zero);
			if (tu_end > TU_MINUTE)
				*pbuffer++ = ':';
		}
		if (tu_start == TU_SECOND) {
			tu_start = writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_SECOND, TU_FRAC, allow_leading_zero);
			if (tu_end > TU_SECOND)
				*pbuffer++ = '.';
		}
		if (tu_start > TU_SECOND)
			writeDigitsAsChars(pbuffer, digit, digit_count, unit_of_digit, TU_FRAC, tu_end + 2, allow_leading_zero);
	}

	*pbuffer = 0;
}
